%%%-------------------------------------------------------------------
%%% @doc  
%%% @end
%%%-------------------------------------------------------------------
-module(process_server).

-behaviour(gen_server).
%%--------------------------------------------------------------------
%% Include files
%%--------------------------------------------------------------------
-include("drproxy_client.hrl").

%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
-export([
	 start_link/0,
	 process/2,
	 process_now/3,
	 process_receiver/4,
	 status/0,
	 resource_available/0,
	 process_loop/1,
	 start_profiling/0,
	 stop_profiling/0
	 ]).

%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	terminate/2, code_change/3]).

%%--------------------------------------------------------------------
%% record definitions
%%--------------------------------------------------------------------

-record(state, {total = 0}).

%%--------------------------------------------------------------------
%% macro definitions
%%--------------------------------------------------------------------
-define(SERVER, ?MODULE).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% @doc Starts the server.
%% @spec start_link() -> {ok, pid()} | {error, Reason}
%% @end
%%--------------------------------------------------------------------
start_link() ->
	gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

process_now(Server, Bin, Attempts) ->
	gen_server:cast(?MODULE, {process_now, Server, Bin, Attempts}).

process(Server, Bin) ->
	gen_server:cast(?MODULE, {process, Server, Bin, 0}).

status() ->
	gen_server:cast(?MODULE, status).

start_profiling() ->
  fprof:trace(start, "process_server.trace").

stop_profiling() ->
  fprof:trace(stop).

resource_available() ->
  process_loop ! resource_available.

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
	dpc_server:empty_queue(),
	register(process_loop, spawn_link(?MODULE, process_loop, [self()])),
	{ok, #state{}}.

%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(Msg, _From, State) ->
	error_logger:error_msg("Invalid call (process_server): ~w~n", [Msg]),
	{reply, ok, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({process_now, Server, Bin, Attempts}, State) ->
	process_loop ! {process_now, Server, Bin, Attempts, State},
	{noreply, State};

handle_cast({process, Server, Bin, Attempts}, State) ->
	process_loop ! {process, Server, Bin, Attempts, State},
	{noreply, State};

handle_cast(status, State) ->
	error_logger:info_msg("Processed: ~w~n", [State#state.total]),
	{noreply, State};

handle_cast(Msg, State) ->
	error_logger:error_msg("Invalid cast (process_server): ~w~n", [Msg]),
	{noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(resource_available, State) ->
	{noreply, State};
handle_info({_, not_allocated}, State) ->
	{noreply, State};
handle_info({_, {allocated, Pid}}, State) ->
	{noreply, State};
handle_info(Info, State) ->
	error_logger:error_msg("Invalid info (process_server): ~w~n", [Info]),
	{noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
	ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> {ok, State}.

%%====================================================================
%%% Internal functions
%%====================================================================

process_loop(Who) ->
  link(Who),
  receive
    {process_now, Server, Bin, Attempts, State} ->
      process_request(process, Server, Bin, Attempts, State),
      process_loop(Who)  
  after 0 ->
    receive
      {process, Server, Bin, Attempts, State} ->
        process_request(process, Server, Bin, Attempts, State),
        process_loop(Who)
    end
  end.

process_request(ProcessType, Server, Bin, Attempts, State) ->
  resource_server:alloc(self()),
	receive
		{allocated, Resource} ->
		  dpc_server:dequeue(),
			resource:alloc(Resource, spawn(?MODULE, process_receiver, [Server, Bin, Resource, Attempts])),
			resource:process(Resource, Server, Bin),
			{reply, ok, State#state{total = State#state.total + 1}};
		not_allocated ->
			% no resources are free, wait for one to free up
			receive
				resource_available ->
				  process_request(ProcessType, Server, Bin, Attempts, State)
				after drproxy_client:config(response_timeout) ->
          error_logger:error_msg("timeout while waiting for resource_available~n"),
				  process_request(ProcessType, Server, Bin, Attempts, State)
			end;
		try_again ->
		  process_request(ProcessType, Server, Bin, Attempts, State)
	end.

process_receiver(Server, Bin, Resource, Attempts) ->
	receive
		{ok, N, BinResponse, ResponseTime} ->
			Server ! {response, node(), N, BinResponse, ResponseTime, Attempts};
		{error, N, request, econnrefused} ->
		  case Attempts > 0 of
		    true ->
    			Server ! {failure, node(), N, request, econnrefused};
    		false ->
    		  resource:monitor(Resource),
    		  process_server:process_now(Server, Bin, Attempts + 1)
    	end;
		{error, N, Type, Details} ->
			Server ! {failure, node(), N, Type, Details};
		X ->
		  error_logger:error_msg("Unexpected message: process_receiver - ~p~n", [X])
	end,
	dpc_server:done().
